package be.kuleuven.thesis.status;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import android.os.AsyncTask;
import be.kuleuven.thesis.databases.AristoMusicManager;
import be.kuleuven.thesis.framework.Filter;
import be.kuleuven.thesis.framework.Song;
import be.kuleuven.thesis.log.Logger;

public class PlaylistGenerator {
	public static final int MAX_NB_SONGS_TO_GENERATE = 5;
	
	private static PlaylistGenerator instance = new PlaylistGenerator();
	private boolean on;
	private Set<Song> likedSongs = new HashSet<Song>();
	private Set<Song> dislikedSongs = new HashSet<Song>();
	private int nbOfSongsToGenerate = 1;
	private Player player;
	private Filter filter = new Filter();
	private Set<StatusListener> listeners = new HashSet<StatusListener>();
	private boolean mutex = false;
	private ArrayList<Song> possibilities = new ArrayList<Song>();
	private boolean outOfDate = false;
	private int duration = 60;
	private Song dummieSong;
	
	private PlaylistGenerator() {
		player = Player.getInstance();
		dummieSong = new Song();
		dummieSong.setTitle("Generating...");
		dummieSong.setArtist("");
	}
	
	public static PlaylistGenerator getInstance() {
		return instance;
	}
	
	public void reset() {
		likedSongs.clear();
		dislikedSongs.clear();
		duration = 60;
	}
	
	public boolean isRadioOn() {
		return on;
	}

	public void turnRadioOn() {
		on = true;
		outOfDate = true;
		checkGeneration();
		Logger.getInstance().log("Gener", "Radio is turned on", null);
		for(StatusListener listener: listeners)
			listener.onRadioSwitched();
	}
	
	public void turnRadioOff() {
		on = false;
		Logger.getInstance().log("Gener", "Radio is turned off", null);
		for(StatusListener listener: listeners)
			listener.onRadioSwitched();
	}

	public int getNbOfSongsToGenerate() {
		return nbOfSongsToGenerate;
	}
	
	public void incrementNbOfSongsToGenerate() {
		if(nbOfSongsToGenerate < MAX_NB_SONGS_TO_GENERATE)
			nbOfSongsToGenerate++;
		checkGeneration();
		Logger.getInstance().log("Gener", "Number of songs changed to "+nbOfSongsToGenerate, null);
	}
	
	public void decrementNbOfSongsToGenerate() {
		if(nbOfSongsToGenerate > 1)
			nbOfSongsToGenerate--;
		checkGeneration();
		Logger.getInstance().log("Gener", "Number of songs changed to "+nbOfSongsToGenerate, null);
	}
	
	public void likeSong(Song song) {
		if(isDisliked(song))
			dislikedSongs.remove(song);
		likedSongs.add(song);
		outOfDate = true;
		checkGeneration();
	}
	
	public boolean isLiked(Song song) {
		return likedSongs.contains(song);
	}
	
	public void unlikeSong(Song song) {
		likedSongs.remove(song);
		outOfDate = true;
	}
	
	public void dislikeSong(Song song) {
		dislikedSongs.add(song);
		if(isLiked(song))
			unlikeSong(song);
	}
	
	public boolean isDisliked(Song song) {
		return dislikedSongs.contains(song);
	}
	
	public Set<Song> getLikedSongs() {
		return likedSongs;
	}
	
	private int getUnlikedSongsAfterPlay() {
		int unlikedSongs = 0;
		for(int i = player.getCurrentIndex()+1; i < player.getCurrentPlaylist().size(); i++) {
			if(!isLiked(player.getCurrentPlaylist().get(i)))
				unlikedSongs++;
		}
		return unlikedSongs;
	}
	
	public int getLikedSongsDuration() {
		int duration = 0;
		for(Song song: Player.getInstance().getCurrentPlaylist()) {
			if(isLiked(song))
				duration = duration + song.getDuration();
		}
		return new Double(Math.round(duration/60000.0)).intValue();
	}
	
	public int getDuration() {
		return duration;
	}
	
	private boolean ranOutOfSongs = false;
	
	public void generateSongs(int number) {
		if(possibilities.size() < number) {
			System.out.println("Songs zijn op");
			if(ranOutOfSongs == true) {
				System.out.println("Oei Aristo faalt");
				for(StatusListener listener: listeners)
					listener.onFiltersTooTight();
				return;
			}
			System.out.println("Ik genereer er wel nieuwe");
			ranOutOfSongs = true;
			new GeneratedSongsTask().execute();
			return;
		}
		ranOutOfSongs = false;
		ArrayList<Song> addedSongs = new ArrayList<Song>();
		for(int i = 0; i < number; i++) {
			Song s = possibilities.get(i);
			player.addGeneratedSong(s);
			addedSongs.add(s);
		}
		possibilities.removeAll(addedSongs);
		checkGeneration();
	}
	
	public void checkGeneration() {
		if(!isRadioOn())
			return;
		if(getUnlikedSongsAfterPlay() < getNbOfSongsToGenerate() && !isGenerating()) {
			if(isOutOfDate()) {
				acquireMutex();
				new GeneratedSongsTask().execute();
			} else {
				int number = getNbOfSongsToGenerate()-getUnlikedSongsAfterPlay();
				generateSongs(number);
			}
		}
	}
	
	public Song getDummieSong() {
		return dummieSong;
	}
	
	public boolean isGenerating() {
		return mutex;
	}
	
	private void acquireMutex() {
		mutex = true;
		player.getCurrentPlaylist().add(dummieSong);
		for(StatusListener listener: listeners)
			listener.onCurrentSongChanged();
	}
	
	private void releaseMutex() {
		mutex = false;
		player.getCurrentPlaylist().remove(dummieSong);
		for(StatusListener listener: listeners)
			listener.onSongRemoved();
		checkGeneration();
	}
	
	private class GeneratedSongsTask extends AsyncTask<Void, Song, Void> {
		ArrayList<Song> possibilities = new ArrayList<Song>();;
		int number = getNbOfSongsToGenerate()-getUnlikedSongsAfterPlay();
		boolean succes = true;
		// automatically done on worker thread (separate from UI thread)
		protected Void doInBackground(Void ...unused) {
			ArrayList<Song> result = (ArrayList<Song>) AristoMusicManager.getInstance().getGeneratedSongs(getLikedSongs(), filter);
			if(result == null) {
				succes = false;
			} else {
				possibilities.addAll(result);
			}
			return null;
		}

		// can use UI thread here
		@Override
		protected void onPostExecute(final Void unused) {
			if(succes == false) {
				turnRadioOff();
				releaseMutex();
				for(StatusListener listener: listeners) {
					listener.onNetworkProblem();
					listener.onRadioSwitched();
					listener.onCurrentSongChanged();
				}
			} else {
				setPossibilities(possibilities);
				outOfDate = false;
				generateSongs(number);
				releaseMutex();
			}
		}
	}

	public Filter getFilter() {
		return filter;
	}

	public void setFilter(Filter filter) {
		this.filter = filter;
		outOfDate = true;
	}
	
	public void applyFilter() {
		//setFilter(filter);
		Logger.getInstance().log("Gener", "Filter Applied", getFilter().toString());
		
		for(int i = player.getPlaylistSize()-1; i >= 0;i--) {
			Song songI = player.getCurrentPlaylist().get(i);
			if(!likedSongs.contains(songI) && !songI.satisfiesFilter(filter) && songI != player.getCurrentSong())
				Player.getInstance().getCurrentPlaylist().remove(i);
		}
		outOfDate = true;
		for(StatusListener listener: listeners)
			listener.onSongRemoved();
		checkGeneration();
	}

	public ArrayList<Song> getPossibilities() {
		return possibilities;
	}

	public void setPossibilities(ArrayList<Song> possibilities) {
		Iterator<Song> it = possibilities.iterator();
		while(it.hasNext()) {
			Song pos = it.next();
			for(Song song: player.getCurrentPlaylist()) {
				if(song.equals(pos)) {
					it.remove();
				}
			}
			for(Song song: dislikedSongs) {
				if(song.equals(pos)) {
					it.remove();
				}
			}
		}
		this.possibilities = possibilities;
	}

	public boolean isOutOfDate() {
		return outOfDate;
	}

	public void setOutOfDate(boolean outOfDate) {
		this.outOfDate = outOfDate;
	}

	public void registerStatusListener(StatusListener listener) {
		listeners.add(listener);
	}
	
	public void unRegisterStatusListener(StatusListener listener) {
		listeners.remove(listener);
	}
}
